/**
 * 
 */
package br.org.bertol.mestrado.engine.optimisation.entity;

import java.util.ArrayList;
import java.util.EnumMap;

import br.org.bertol.mestrado.engine.fitness.AbstractCombinatorialFitness;
import br.org.bertol.mestrado.engine.fitness.Objective;
import br.org.bertol.mestrado.engine.optimisation.pso.LinearInertiaReduction;
import br.org.bertol.mestrado.engine.optimisation.pso.Velocity;

/**
 * @author contaqualquer
 */
public class Particle<T> extends AbstractUnit<T> {

    /**
     * Velocidade da partícula.
     */
    private transient ArrayList<Velocity<T>>       speed;

    /**
     * Melhor caminho da partícula.
     */
    private transient ArrayList<T>                 pbest;

    /**
     * Peso do melhor caminho da partícula.
     */
    private transient EnumMap<Objective, Number>   hpbest;

    /**
     * Melhor caminho global.
     */
    private transient ArrayList<T>                 gbest;

    /**
     * Peso do melhor caminho global.
     */
    private transient EnumMap<Objective, Number>   hgbest;

    /**
     * Taxa de redução da inércia.
     */
    private final transient LinearInertiaReduction linearReduction;

    /**
     * Fator cognitivo.
     */
    private final transient float                  fCognitive;

    /**
     * Fator social.
     */
    private final transient float                  fSocial;

    /**
     * Fator de constrição da velocidade.
     */
    private final transient float                  fConstric;

    /***/
    private transient int                          espacialPosition;

    /**
     * Cria uma nova partícula.
     * @param objectives
     *            Objetivos do problema
     * @param lReduction
     *            Redução linear da inércia
     * @param fcognitive
     *            Fator cognitivo
     * @param fsocial
     *            Fator social
     */
    public Particle(final LinearInertiaReduction lReduction,
        final float fcognitive, final float fsocial,
        final AbstractCombinatorialFitness<T> fitness,
        final Objective... objectives) {
        super(fitness, objectives);

        speed = new ArrayList<Velocity<T>>();

        pbest = new ArrayList<T>();

        hpbest = new EnumMap<Objective, Number>(Objective.class);

        gbest = new ArrayList<T>();

        hgbest = new EnumMap<Objective, Number>(Objective.class);

        // caso os fatores de multiplica��o sejam negativos
        // multiplica todos por -1
        this.linearReduction = lReduction;

        this.fCognitive = fcognitive < 0 ? fcognitive * -1
                : fcognitive;

        this.fSocial = fsocial < 0 ? fsocial * -1 : fsocial;

        final float t = (float) (2 / (2 - (this.fSocial + this.fCognitive) - Math
                .sqrt(Math.pow((this.fSocial + this.fCognitive), 2)
                - (4 * (this.fSocial + this.fCognitive)))));

        this.fConstric = t < 0 ? t * -1 : t;
    }

    /**
     * Atualiza p best.
     */
    @SuppressWarnings("unchecked")
    public final void updatePBest() {
        // atualiza caminho
        pbest = (ArrayList<T>) (route).clone();

        // atualiza peso
        hpbest = height.clone();
    }

    /**
     * Atualiza g best.
     */
    @SuppressWarnings("unchecked")
    public final void updateGbest() {
        gbest = (ArrayList<T>) (pbest).clone();

        // atualiza peso
        hgbest = hpbest.clone();
    }

    /**
     * @param particle
     *            PArtícula a ter os elementos capturados
     */
    @SuppressWarnings("unchecked")
    public final void updateGbest(final Particle particle) {
        gbest = (ArrayList<T>) particle.getRoute().clone();

        hgbest = particle.getHeight().clone();
    }

    /**
     * Calcula a velocidade da part�cula.
     * @param kIteration
     *            Iteração atual
     */
    @SuppressWarnings("unchecked")
    public final void updateVelocity(final int kIteration) {

        final ArrayList<Velocity<T>> gBestSpeed =
                (ArrayList<Velocity<T>>) multiplication(gbest,
                                                        route,
                                                        fSocial)
                .clone();

        final ArrayList<Velocity<T>> pBestSpeed =
                (ArrayList<Velocity<T>>) multiplication(pbest,
                                                        route,
                                                        fCognitive)
                .clone();

        final ArrayList<Velocity<T>> vXInertia =
                (ArrayList<Velocity<T>>) multiplication(
                                                        speed,
                                                        linearReduction
                .get(kIteration))
                .clone();

        gBestSpeed.trimToSize();

        pBestSpeed.addAll(gBestSpeed);

        pBestSpeed.trimToSize();

        vXInertia.addAll(pBestSpeed);

        vXInertia.trimToSize();

        // inverte a ordem
        // Collections.reverse(V_x_inertia);

        // atualiza velocidade
        speed = multiplication(vXInertia, fConstric);

    }

    /**
     * Realiza multiplica��o do array de velocidade com a inércia.
     * @param velocity
     *            Array de velocidade
     * @param factor
     *            Fator de multiplicação
     * @return Array de velocidade
     */
    private ArrayList<Velocity<T>> multiplication(
            final ArrayList<Velocity<T>> velocity,
            final float factor) {
        // caso fator de multiplica��o seja 0, retorna
        if (factor == 0) {
            return new ArrayList<Velocity<T>>(0);
        }

        // entre 0 e 1, truca o array de velocidades
        if (factor < 1 && factor > 0) {
            return multiplicationLower(velocity, factor);
        }

        return multiplicationUpper(velocity, factor);
    }

    /**
     * Realiza a multiplica��o do array dos arrays de PBest/GBest com a posi��o
     * da ppartácula e os fatores sociais/cognitivos.
     * @param left
     *            Array de psoições
     * @param right
     *            Array de psoições
     * @param factor
     *            fator de multiplicação
     * @return Novo array de velocidade
     */
    private ArrayList<Velocity<T>> multiplication(final ArrayList<T> left,
            final ArrayList<T> right, final float factor) {
        // caso fator de multiplica��o seja 0, retorna
        if (factor == 0) {
            return new ArrayList<Velocity<T>>(0);
        }

        final Float multiplicationFactor = factor * (float) Math.random();

        // entre 0 e 1, truca o array de velocidades
        if (multiplicationFactor < 1 && multiplicationFactor > 0) {
            return multiplicationLower(subtraction(left, right),
                                       multiplicationFactor);
        }

        return multiplicationUpper(subtraction(left, right),
                                   multiplicationFactor);
    }

    /**
     * Multiplica arrays com fatores negativos.
     * @param speedArray
     *            Array de velocidade
     * @param mFactor
     *            Fator de multiplicação
     * @return Noovo array de velocidade
     */
    private ArrayList<Velocity<T>> multiplicationLower(
            final ArrayList<Velocity<T>> speedArray,
            final float mFactor) {
        // trunca o array em quest�o, retornando o array truncado
        final Float truncateArray = mFactor * speedArray.size();

        final ArrayList<Velocity<T>> trucatedSpeed = new ArrayList<Velocity<T>>(
                speedArray.subList(0, truncateArray.intValue()));

        return trucatedSpeed;
    }

    /**
     * Multiplica arrays com fatores positivos.
     * @param speedArray
     *            Array de velocidade
     * @param mFactor
     *            Fator de multiplicação
     * @return Novo array de velocidade
     */
    private ArrayList<Velocity<T>> multiplicationUpper(
            final ArrayList<Velocity<T>> speedArray,
            final Float mFactor) {

        final ArrayList<Velocity<T>> newSpeedArray =
                new ArrayList<Velocity<T>>(0);

        // parte decimal do array de velocidades
        final Float decimal = mFactor
                - mFactor.intValue();

        // soma o array com as partes inteiras
        for (int i = 0; i < mFactor.intValue(); i++) {
            newSpeedArray.addAll(speedArray);
        }

        // adiciona a parte fracion�ria
        newSpeedArray.addAll(multiplicationLower(newSpeedArray, decimal));

        return newSpeedArray;
    }

    /**
     * Recebe as listas de velocidade e pbest ou gbest, dependendo da situaçãoo
     * e monta novo array de velocidade para cálculo.
     * @param array1
     *            Array de posição 1
     * @param array2
     *            Array de posição 2
     * @return Array com as permutações
     */
    private ArrayList<Velocity<T>> subtraction(final ArrayList<T> array1,
            final ArrayList<T> array2) {
        final ArrayList<Velocity<T>> newVelocity =
                new ArrayList<Velocity<T>>(0);

        for (T leftItem : array1) {

            final int leftIndex = array1.indexOf(leftItem);

            if (leftIndex <= array2.size()) {

                final T rightItem = array2.get(leftIndex);

                final int rightIndex = array2.indexOf(rightItem);

                if (rightIndex > -1) {
                    newVelocity.add(new Velocity<T>(leftItem, rightItem));
                }
            }
        }

        return newVelocity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String toString() {
        final StringBuilder buffer = new StringBuilder();

        buffer.append("Posição no espaço: ").append("[")

        .append(espacialPosition).append("] |       ");

        buffer.append(super.toString());

        buffer.append(" | Melhor resultado").append("[")
                .append(hpbest.toString())
                .append("]");

        return buffer.toString();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public final Object clone() throws CloneNotSupportedException {
        final Particle clone = (Particle) super.clone();

        clone.pbest = (ArrayList<Integer>) this.pbest.clone();

        clone.gbest = (ArrayList<Integer>) this.gbest.clone();

        clone.speed = (ArrayList<Velocity>) this.speed.clone();

        clone.hpbest = this.hpbest.clone();

        clone.hgbest = this.hgbest.clone();

        return clone;
    }

    @Override
    public final void update() {
        updatePBest();
    }

    /**
     * @return the hbest
     */
    public final EnumMap<Objective, Number> getHpbest() {
        return hpbest;
    }

    /**
     * @return the speed
     */
    public final ArrayList<Velocity<T>> getSpeed() {
        return speed;
    }

    /**
     * Posição espacial da partícula noespaço de busca.
     * @return int Posição
     */
    public final int getSpacialPosition() {
        return espacialPosition;
    }

    /**
     * Posição das partícula no espaço.
     * @param position
     *            a posição
     */
    public final void setSpacialPosition(final int position) {
        this.espacialPosition = position;
    }
}
